/*
Hermes Core: General Purpose Mobile Agent Platform
Copyright (C) 2006 UNICAM - Università degli Studi di Camerino

Group leaders: Flavio Corradini and Emanuela Merelli

Developers: Ezio Bartocci, Diego Bonura, Rosario Culmone,
Davide Lorenzoli, Leonardo Mariani, Francesca Piersigilli, 
Lorenzo Scortichini, Leonardo Vito.

GNU Lesser Public License

This library is free software; you can redistribute it 
and/or modify it under the terms of the GNU Lesser General 
Public License as published by the Free Software Foundation, 
version 2.1 of the License.

This library is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of 
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
See the GNU Library General Public License for more details.

You should have received a copy of the GNU Library General Public 
License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.

Emanuela Merelli
Dipartimento di Matematica ed Informatica
Università di Camerino
via Madonna delle Carceri, 62032 Camerino, Italy
E.Mail: emanuela.merelli@unicam.it
*/
package hermesV2.core.id;

import hermesV2.AgentThread;
import hermesV2.PlaceAddress;

import hermesV2.agent.Identificator;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Observable;
import java.util.Set;

/**
 * This Class has the ability to manage identificators and to trace agents
 * 
 * @author Diego Bonura
 */
public class IdManager extends Observable implements Identify, Trace {
	/**
	 * Contains <CODE>Identificator</CODE> s of agents been born in the
	 * platform.
	 */
	private HashMap<String, PlaceAddress>  bornsHashMap;

	/**
	 * It contains the <CODE>Identificator</CODE> s of agents currently on the
	 * platform.
	 */
	private HashMap<String,  AgentTableContainer>  agentsHashMap;

	/**
	 * The synchronizedMap of the bornsHashMap.
	 */
	private Map<String, PlaceAddress>  bornsMap;

	/**
	 * The synchronizedMap of the agentsHashMap.
	 */
	private Map<String,  AgentTableContainer>  agentsMap;

	/** The address of the current platform. */
	private PlaceAddress currentPlaceAddress;

	/**
	 * <CODE>IDManager</CODE> is the manager of agent identificator that has
	 * the responsability to manage Identificator of agent currently present in
	 * the platform and Identificator of agent been born in the current place.
	 * By this way it is responsable to trace every migration of agents borned
	 * in the current place.
	 * 
	 * @param currentPlaceAddress
	 *            The current place address of the platform.
	 */
	public IdManager(PlaceAddress currentPlaceAddress) {
		this.currentPlaceAddress = currentPlaceAddress;
		bornsHashMap = new HashMap<String, PlaceAddress> ();
		agentsHashMap = new HashMap<String,  AgentTableContainer> ();
		bornsMap = Collections.synchronizedMap(bornsHashMap);
		agentsMap = Collections.synchronizedMap(agentsHashMap);
	}

	/**
	 * Creates a new <CODE>Identificator</CODE> for <CODE>UserAgent</CODE>
	 * 
	 * @param name
	 *            The name of the new agent.
	 * @return <CODE>Identificator</CODE>
	 */
	public Identificator createNewUserAgentIdentificator(String name) {
		return new Identificator(currentPlaceAddress, name, false);
	}

	/**
	 * Creates a new <CODE>Identificator</CODE> for <CODE>ServiceAgent
	 * </CODE>
	 * 
	 * @param name
	 *            The name of the new agent.
	 * @return <CODE>Identificator</CODE>
	 */
	public Identificator createNewServiceAgentIdentificator(String name) {
		return new Identificator(currentPlaceAddress, name, true);
	}

	/**
	 * Returns the <CODE>PlaceAddress</CODE> of a specific <CODE>UserAgent
	 * </CODE>, if the UserAgent is not born or not running in the current
	 * place returns null.
	 * 
	 * @param id
	 *            The <CODE>Identificator</CODE> of the <CODE>UserAgent
	 *            </CODE>
	 * 
	 * @return the <CODE>PlaceAddress</CODE> where the <CODE>UserAgent
	 *         </CODE> resides.
	 */
	public PlaceAddress getCurrentAddress(Identificator id) {
		PlaceAddress pa = null;
		if (isAgentRunningHere(id.toString())) {
			pa = currentPlaceAddress;
		} else {
			pa = bornsMap.get(id.toString());
		}
		return pa;
	}

	
	
	/**
	 * Checks if the agent is actually in the current platform.
	 * @param id The agent identificator to check.
	 * @return true if the agent is on the current platform false otherwise.
	 */
	private boolean isAgentRunningHere(String id) {
		return agentsMap.containsKey(id);
	}

	/**
	 * Returns the <CODE>PlaceAddress</CODE> of a specific <CODE>UserAgent
	 * </CODE>, if the UserAgent is not born or not running in the current
	 * place returns null.
	 * 
	 * @param id
	 *            The string of the <CODE>Identificator</CODE> of the <CODE>
	 *            UserAgent</CODE>.
	 * 
	 * @return the <CODE>PlaceAddress</CODE> where the <CODE>UserAgent
	 *         </CODE> resides.
	 */
	public PlaceAddress getCurrentAddress(String id) {
		PlaceAddress pa = null;
		if (isAgentRunningHere(id)) {
			pa = currentPlaceAddress;
		} else {
			pa = bornsMap.get(id);
		}
		return pa;
	}

	/**
	 * Updates the current position of an <CODE>UserAgent</CODE> born in the
	 * current place
	 * 
	 * @param id
	 *            The <CODE>Identificator</CODE> of the <CODE>UserAgent
	 *            </CODE>
	 * @param pa
	 *            The current <CODE>PlaceAddress</CODE> of the <CODE>
	 *            UserAgent</CODE>
	 * @return The place address where the agent is located now.
	 */
	public PlaceAddress updateAgentAddress(Identificator id, PlaceAddress pa) {
		PlaceAddress pa_temp;

		if (bornsMap.containsKey(id.toString())) {
			bornsMap.remove(id.toString());
			bornsMap.put(id.toString(), pa);
			pa_temp = pa;
			setChanged();
			notifyObservers(id);
		} else {
			pa_temp = null;
		}

		return pa_temp;
	}

	/**
	 * Adds the Agent <CODE>Identificator</CODE> to the plattform
	 * 
	 * @param at
	 *            The AgentThread associated to the agent running.
	 * @param id
	 *            The <CODE>Identificator</CODE> of the Agent
	 */
	public void addAgentIdentificator(Identificator id, AgentThread at) {
		System.out.println("------------------------------------------------------------------------");
		agentsMap.put(id.toString(), new AgentTableContainer(id, at));
		setChanged();
	    notifyObservers(id);
	    clearChanged();
	}

	/**
	 * Adds the Agent (born in the current place) Identificator to the platform
	 * 
	 * @param placeAddress
	 *            The place address where the agent is born.
	 * @param id
	 *            The Agent <CODE>Identificator</CODE>
	 */
	public void addBornAgentIdentificator(Identificator id,
			PlaceAddress placeAddress) {
		System.out.println("------------------------------------------------------------------------");
		bornsMap.put(id.toString(), placeAddress);
		setChanged();
		notifyObservers(id);
		clearChanged();
	}

	/**
	 * Searches the identificator into the agentTable hashtable and returns the
	 * value.
	 * 
	 * @param id
	 *            The identificator of the agent.
	 * 
	 * @return The value into the hastable (AgentThread) or null.
	 */
	public AgentThread getAgentThread(Identificator id) {
		AgentThread a;

		synchronized (agentsHashMap) {
			AgentTableContainer atc =  agentsHashMap
					.get(id.toString());

			if (atc != null) {
				a = atc.getAgentThread();
			} else {
				a = null;
			}
		}

		return a;
	}

	/**
	 * Returns the Identificator object of an agent running into the current
	 * place.
	 * 
	 * @param id
	 *            The string of the identificator.
	 * 
	 * @return The Identificator object.
	 */
	public Identificator getAgentIdentificator(String id) {
		return  agentsMap.get(id).getIdentificator();
	}

	/**
	 * This method erases the agent identificator into the agentsTable.
	 * 
	 * @param id
	 *            The identificator of the agent.
	 * 
	 * @return True if the identificator is on the table False otherwise.
	 */
	public boolean eraseAgentIdentificator(Identificator id) {
		System.out.println("------------------------------------------------------------------------");
		boolean success;
		if (agentsMap.remove(id.toString()) == null) {
			success = false;
			setChanged();
			notifyObservers(id);
			clearChanged();
		} else {
			success = true;
		}
		
		return success;
	}

	/**
	 * This method erases the agent entry into the bornsTable so will be
	 * impossible to trace the agent anymore.
	 * 
	 * @param id
	 *            The identificator of the agent.
	 * 
	 * @return True if the identificator is on the table False otherwise.
	 */
	public boolean eraseAgentAddress(Identificator id) {
		System.out.println("------------------------------------------------------------------------");
		boolean success;
		if (bornsMap.remove(id.toString()) == null) {
			success = false;
			setChanged();
			notifyObservers(id);
			clearChanged();
		} else {
			success = true;
		}
		
		return success;
	}

	/**
	 * This method returns all the indentificators of the current agents present
	 * into the place.
	 * 
	 * @return an array of identificators.
	 */
	public synchronized Identificator[] getAllAgentsIdentificators() {
		Identificator[] identificators = null;
			Collection<AgentTableContainer> agentCollection = agentsHashMap.values();
			Iterator<AgentTableContainer> agentsIterator = agentCollection.iterator();
			int countId = agentCollection.size();
			identificators = new Identificator[countId];

			int i = 0;

			while (agentsIterator.hasNext()) {
				identificators[i] = agentsIterator
						.next().getIdentificator();
				i++;
			}
 	return identificators;
	}

	/**
	 * This method returns all the identificators of the born agent hash map.
	 * 
	 * @return an array of string of identificators.
	 */
	public synchronized String[] getAllBornsAgentsIdentificators() {
		String[] identificators = null;
			Set<String> bornAgentsKeySet = bornsHashMap.keySet();
			int countId = bornAgentsKeySet.size();
			Iterator<String> bornAgentsIterator = bornAgentsKeySet.iterator();
			identificators = new String[countId];

			int i = 0;

			while (bornAgentsIterator.hasNext()) {
				identificators[i] = bornAgentsIterator.next();
				i++;
			}
		return identificators;
	}
}